// --------------------------------------------------------------------------------------------------------------------
// <copyright file="RoleEnvironment.cs" company="AzureContrib (Open source library http://azurecontrib.codeplex.com)">
//   Microsoft Public License (Ms-PL)
// </copyright>
// <summary>
//   Exception for errors when handling Windows Azure Cloud Drives.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Permissions;

using AzureContrib.WindowsAzure.Diagnostics;
using AzureContrib.WindowsAzure.ServiceRuntime.Properties;

using Microsoft.WindowsAzure.ServiceRuntime;

using MSRoleEnvironment = Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment;
using MSRoleEnvironmentException = Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironmentException;
using SD = System.Diagnostics;

namespace AzureContrib.WindowsAzure.ServiceRuntime
{
	/// <summary>
	/// A small manager class to easily and safely access the <see cref="Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment.GetConfigurationSettingValue"/>
	/// </summary>
	/// <remarks>Will implement [Export(typeof(IRoleEnvironment))]</remarks>
	public class RoleEnvironment : IRoleEnvironment
	{
		/// <summary>
		/// Initializes a new instance of the <see cref="RoleEnvironment"/> class. 
		/// </summary>
		public RoleEnvironment()
			: this(new Trace(typeof(RoleEnvironment), new VerboseCatchallTraceSwitchProvider()))
		{
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="RoleEnvironment"/> class. 
		/// </summary>
		/// <param name="trace">The current <see cref="ITrace"/>.</param>
		public RoleEnvironment(ITrace trace)
		{
			Assumes.NotNull(trace, "trace");
			RealRoleEnvironment = new WrappedRoleEnvironment(trace);
		}

		/// <summary>
		/// Wrapper for <see cref="Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment.Changing"/>.
		/// </summary>
		public event EventHandler<RoleEnvironmentChangingEventArgs> Changing
		{
			add { RealRoleEnvironment.Changing += value; }
			remove { RealRoleEnvironment.Changing -= value; }
		}

		/// <summary>
		/// Wrapper for <see cref="Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment.Changed"/>.
		/// </summary>
		public event EventHandler<RoleEnvironmentChangedEventArgs> Changed
		{
			add { RealRoleEnvironment.Changed += value; }
			remove { RealRoleEnvironment.Changed -= value; }
		}

		/// <summary>
		/// Gets or sets he real wrapped RoleEnvironment that is called. This may be mocked in tests
		/// </summary>
		public IRoleEnvironment RealRoleEnvironment { get; set; }

		/// <summary>
		/// Gets the Id of the current Azure deployment.
		/// </summary>
		public string DeploymentId
		{
			get { return RealRoleEnvironment.DeploymentId; }
		}

		/// <summary>
		/// Gets the wrapped <see cref="Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment.CurrentRoleInstance"/>.
		/// </summary>
		/// <remarks>Wrapped by the contract <seealso cref="IRoleInstance"/>.</remarks>
		public IRoleInstance CurrentRoleInstance
		{
			get { return RealRoleEnvironment.CurrentRoleInstance; }
		}

		/// <summary>
		/// Gets a value indicating whether the RoleEnvironment is available?
		/// </summary>
		public bool IsAvailable
		{
			get { return RealRoleEnvironment.IsAvailable; }
		}

		/// <summary>
		/// Gets the named <see cref="IRole"/>s running in the <see cref="IRoleEnvironment"/>.
		/// </summary>
		public IDictionary<string, IRole> Roles
		{
			get
			{
				return RealRoleEnvironment.Roles;
			}
		}

		/// <summary>
		/// Get a configuration setting from <see cref="Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment.GetConfigurationSettingValue"/>
		/// </summary>
		/// <param name="configurationSettingName">The name of the configuration setting to fetch.</param>
		/// <param name="settingValue">The configuration setting value.</param>
		/// <returns>True if the setting is present.</returns>
		public bool TryGetConfigurationSettingValue(string configurationSettingName, out string settingValue)
		{
			if (RealRoleEnvironment.IsAvailable)
			{
				return RealRoleEnvironment.TryGetConfigurationSettingValue(configurationSettingName, out settingValue);
			}

			settingValue = ConfigurationManager.AppSettings[configurationSettingName];

			return settingValue != null;
		}

		/// <summary>
		/// Requests that the current role instance be stopped and restarted.
		/// </summary>
		public void RequestRecycle()
		{
			RealRoleEnvironment.RequestRecycle();
		}

		/// <summary>
		/// Get a configuration setting from <see cref="Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment.GetConfigurationSettingValue"/>
		/// </summary>
		/// <param name="configurationSettingName">The name of the configuration setting to fetch.</param>
		/// <returns>The configuration setting value.</returns>
		public string GetConfigurationSettingValue(string configurationSettingName)
		{
			return RealRoleEnvironment.IsAvailable ?
				RealRoleEnvironment.GetConfigurationSettingValue(configurationSettingName) :
			    ConfigurationManager.AppSettings[configurationSettingName];
		}

		/// <summary>
		/// Returns a reference to a <see cref="ILocalResource"/>
		/// </summary>
		/// <param name="localResourceName">The configured name of the local resource.</param>
		/// <returns>The actual instance of the <see cref="ILocalResource"/>.</returns>
		public ILocalResource GetLocalResource(string localResourceName)
		{
			return RealRoleEnvironment.GetLocalResource(localResourceName);
		}

		internal class WrappedRoleEnvironment : IRoleEnvironment, IDisposable
		{
			private ITrace trace;
			private bool disposed;

			[EnvironmentPermissionAttribute(SecurityAction.LinkDemand, Unrestricted = true)]
			public WrappedRoleEnvironment(ITrace trace)
			{
				this.trace = trace;
				MSRoleEnvironment.Changed += MSRoleEnvironment_Changed;
				MSRoleEnvironment.Changing += MSRoleEnvironment_Chaning;
			}

			public event EventHandler<RoleEnvironmentChangedEventArgs> Changed;

			public event EventHandler<RoleEnvironmentChangingEventArgs> Changing;

			public string DeploymentId
			{
				get { return MSRoleEnvironment.DeploymentId; }
			}

			public bool IsAvailable
			{
				get
				{
					return MSRoleEnvironment.IsAvailable;
				}
			}

			public IDictionary<string, IRole> Roles
			{
				get
				{
					var dictionary = new Dictionary<string, IRole>();
					MSRoleEnvironment.Roles.ForEach(kvp => dictionary.Add(kvp.Key, new RoleWrapper(kvp.Value)));
					return dictionary;
				}
			}

			public IRoleInstance CurrentRoleInstance
			{
				get { return new RoleInstanceWrapper(MSRoleEnvironment.CurrentRoleInstance); }
			}

			public void Dispose()
			{
				Dispose(true);
				GC.SuppressFinalize(this);
			}

			public string GetConfigurationSettingValue(string roleSettingName)
			{
				string setting;

				if (!TryGetConfigurationSettingValue(roleSettingName, out setting))
				{
					SD.Trace.TraceWarning(Strings.RoleSettingNonExistant, roleSettingName);
					setting = null;
				}

				return setting;
			}

			/// <summary>
			/// Get a configuration setting from <see cref="Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment.GetConfigurationSettingValue"/>
			/// </summary>
			/// <param name="configurationSettingName">The name of the configuration setting to fetch.</param>
			/// <param name="settingValue">The configuration setting value.</param>
			/// <returns>True if the setting is present.</returns>
			public bool TryGetConfigurationSettingValue(string configurationSettingName, out string settingValue)
			{
				try
				{
					settingValue = MSRoleEnvironment.GetConfigurationSettingValue(configurationSettingName);
				}
				catch (SEHException)
				{
					// When not in Azure or Azure emulator
					settingValue = null;
				}
				catch (MSRoleEnvironmentException)
				{
					// When in Azure or Azure emulator
					settingValue = null;
				}
				catch (ConfigurationException ex)
				{
					trace.Error(Strings.ExceptionWhenFetchingResource, ex);
					settingValue = null;
				}

				return settingValue != null;
			}

			public ILocalResource GetLocalResource(string localResourceName)
			{
				try
				{
					LocalResource localResource = MSRoleEnvironment.GetLocalResource(localResourceName);
					LocalResourceWrapper localResourceWrapper = new LocalResourceWrapper(localResource);
					return localResourceWrapper;
				}
				catch (SEHException sehe)
				{
					var message = Strings.UnableToAccessLocalResource.F(localResourceName);
					SD.Trace.TraceError(message);
					throw new RoleEnvironmentException(message, sehe);
				}
			}

			/// <summary>
			/// Requests that the current role instance be stopped and restarted.
			/// </summary>
			public void RequestRecycle()
			{
				MSRoleEnvironment.RequestRecycle();
			}

			protected virtual void Dispose(bool disposing)
			{
				if (disposed)
				{
					return;
				}

				if (disposing)
				{
					MSRoleEnvironment.Changed -= MSRoleEnvironment_Changed;
					MSRoleEnvironment.Changing -= MSRoleEnvironment_Chaning;
				}

				disposed = true;
			}

			private void MSRoleEnvironment_Chaning(object sender, Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironmentChangingEventArgs e)
			{
				var changingListeners = Changing;

				if (changingListeners == null)
				{
					return;
				}

				var changes = e.Changes.Select(CreateRoleEnvironmentChange);

				changingListeners(this, new RoleEnvironmentChangingEventArgs(changes));
			}

			private void MSRoleEnvironment_Changed(object sender, Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironmentChangedEventArgs e)
			{
				var changedListeners = Changed;

				if (changedListeners == null)
				{
					return;
				}

				var changes = e.Changes.Select(CreateRoleEnvironmentChange);

				changedListeners(this, new RoleEnvironmentChangedEventArgs(changes));
			}

			private RoleEnvironmentChange CreateRoleEnvironmentChange(Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironmentChange roleEnvironmentChange)
			{
				var roleEnvironmentConfigurationSettingChange = roleEnvironmentChange as Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironmentConfigurationSettingChange;
				if (roleEnvironmentConfigurationSettingChange != null)
				{
					var configurationSettingName = roleEnvironmentConfigurationSettingChange.ConfigurationSettingName;
					return new RoleEnvironmentConfigurationSettingChange(configurationSettingName);
				}

				var roleEnvironmentTopologyChange = roleEnvironmentChange as Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironmentTopologyChange;
				if (roleEnvironmentTopologyChange != null)
				{
					return new RoleEnvironmentTopologyChange(roleEnvironmentTopologyChange.RoleName);
				}

				throw new ConfigurationErrorsException("Unknown event arg type {0}".F(roleEnvironmentChange.GetType().FullName));
			}
		}
	}
}